using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using System.IO;
using System.Text.RegularExpressions;

namespace Campus
{
    #region Level Enums

    public enum Terrain
    {
        Undefined,
        Water1, Water2, Water3, Water4,
        Dirt1, Dirt2, Dirt3,
        Swamp1, Swamp2, Swamp3,
        Grass, Stone, Desert, Cave,
        Hillside, Ramp,
        Mound,
        Mountain, Tree, Boulder,
        PlayerStart1, PlayerStart2, PlayerStart3, PlayerStart4,
        MG1a, MG1b, MG1c, MG1d, MG1e,
        Pavement,
        WeaveA, WeaveB, WeaveC, WeaveD
    }

    #endregion

    public class World
    {
        #region Fields and Properties

        /// <summary>
        /// The number of rows of tiles in this World object.
        /// </summary>
        public int Height
        {
            get { return m_rows; }
        }
        private int m_rows;

        /// <summary>
        /// The number of columns of tiles in this World object.
        /// </summary>
        public int Width
        {
            get { return m_columns; }
        }
        private int m_columns;

        /// <summary>
        /// The 2D array which contains the list of terrain types on each map space.
        /// </summary>
        public List<Terrain>[,] TerrainGrid
        {
            get { return m_terrainGrid; }
        }
        private List<Terrain>[,] m_terrainGrid;

        /// <summary>
        /// This is the legend for map files.  You can change how characters get interpreted by adding and removing entries that map characters in the map file to Terrain enum types.
        /// </summary>
        public Dictionary<string, Terrain> MapLegend
        {
            get { return m_mapLegend; }
        }
        private Dictionary<string, Terrain> m_mapLegend = new Dictionary<string, Terrain>()
        {
            { "*", Terrain.Undefined },
            { "W1", Terrain.Water1 },
            { "W2", Terrain.Water2 },
            { "W3", Terrain.Water3 },
            { "G", Terrain.Grass },
            { "D1", Terrain.Dirt1 },
            { "D2", Terrain.Dirt2 },
            { "D3", Terrain.Dirt3 },
            { "S1", Terrain.Swamp1 },
            { "S2", Terrain.Swamp2 },
            { "S3", Terrain.Swamp3 },
            { "H", Terrain.Hillside },
            { "R", Terrain.Ramp },
            { "M", Terrain.Mound },
            { "T", Terrain.Tree },
            { "B", Terrain.Boulder },
            { "1", Terrain.PlayerStart1 },
            { "2", Terrain.PlayerStart2 },
            { "3", Terrain.PlayerStart3 },
            { "4", Terrain.PlayerStart4 },
            { "Ga", Terrain.MG1a },
            { "Gb", Terrain.MG1b },
            { "Gc", Terrain.MG1c },
            { "Gd", Terrain.MG1d },
            { "Ge", Terrain.MG1e },
            { "P", Terrain.Pavement },
            { "Wa", Terrain.WeaveA },
            { "Wb", Terrain.WeaveB },
            { "Wc", Terrain.WeaveC },
            { "Wd", Terrain.WeaveD }
        };

        /// <summary>
        /// The character which denotes the end of a line in a mapfile.
        /// </summary>
        public char EndOfLineCharacter
        {
            get { return m_endOfLineCharacter; }
        }
        private char m_endOfLineCharacter = '-';

        /// <summary>
        /// The string representation of the currently-loaded map.
        /// </summary>
        public string LoadedMap
        {
            get { return m_loadedMap; }
        }
        private string m_loadedMap = "";

        /// <summary>
        /// The parent of this World object; or the object that keeps a reference to it.
        /// </summary>
        public Level Parent
        {
            get { return m_parent; }
        }
        private Level m_parent;

        /// <summary>
        /// The PathFinder object used to determine paths around this Level object.
        /// </summary>
        public PathFinder PathFinder
        {
            get { return m_pathFinder; }
        }
        private PathFinder m_pathFinder;

        /// <summary>
        /// The list of available player starting points in terms of the game grid.
        /// </summary>
        public List<Vector2> PlayerStartPoints
        {
            get { return m_playerStartPoints; }
        }
        private List<Vector2> m_playerStartPoints = new List<Vector2>();

        #endregion

        #region Constructors

        /// <summary>
        /// Loads a WorldBuilder object from a string representing the map.
        /// 
        /// See the example Content/Level/map1.txt file for an example map string.  The legend is as follows:
        /// 
        /// , = End of Layer Definition
        /// ; = End of Tile Definition
        /// - = End of Line
        /// * = Undefined (outside the isometric world - unwalkable)
        /// W = Water (un-walkable)
        /// G = Grass (walkable)
        /// D = Dirt (walkable)
        /// S = Swamp (walkable)
        /// H = Hillside (unwalkable)
        /// R = Ramp (walkable, connects raised Mound to Grass)
        /// M = Mound (raised, walkable)
        /// T = Tree (unwalkable)
        /// B = Boulder (unwalkable)
        /// # = # Player Start
        /// </summary>
        /// <param name="parent">The Level object that this World belongs to.</param>
        /// <param name="map">The string representing the map.</param>
        /// <param name="unwalkableTiles">The list of Terrain enums which count as walls.</param>
        public World(Level parent, string map, List<Terrain> unwalkableTerrainTypes)
        {
            m_parent = parent;
            m_loadedMap = map;

            // Collect non-empty lines
            string[] split = map.Split(m_endOfLineCharacter);
            List<string> nonEmptyLines = new List<string>();

            for (int i = 0; i < split.Length; i++)
                if (split[i].Trim() != "")
                    nonEmptyLines.Add(split[i]);

            string[] lines = nonEmptyLines.ToArray();

            // Set columns and rows from line lengths and number of lines
            m_columns = lines[0].Split(';').Length;
            m_rows = lines.Length;

            // Create the grid and initialize to Undefined
            m_terrainGrid = new List<Terrain>[m_rows, m_columns];

            // Fill in the grid from the lines
            for (int r = 0; r < m_rows; r++)
            {
                string currentLine = lines[r].Trim();

                // Split the current line into their tile listings.
                string[] tileListings = currentLine.Split(';');

                for (int c = 0; c < m_columns; c++)
                {
                    // Create the new list we're about to add to.
                    m_terrainGrid[r, c] = new List<Terrain>();

                    // Split the current tile listing into the tile strings
                    string[] currentTile = tileListings[c].Trim().Split(',');

                    for (int s = 0; s < currentTile.Length; s++)
                    {
                        string currentTileString = currentTile[s];

                        // Set the grid to the terrain provided by the legend if it exists.
                        if (m_mapLegend.ContainsKey(currentTileString))
                        {
                            m_terrainGrid[r, c].Add(m_mapLegend[currentTileString]);

                            // If the character is a number, it must be a player start point so take note.
                            int result;
                            if (int.TryParse(currentTileString, out result))
                            {
                                // Store the start point in standard (x,y) fashion rather than (r,c) to avoid confusion.
                                m_playerStartPoints.Add(new Vector2(c, r));
                            }
                        }
                        else
                            m_terrainGrid[r, c].Add(Terrain.Undefined);
                    }
                }
            }

            m_pathFinder = new PathFinder(this, unwalkableTerrainTypes);
        }

        #endregion

        #region Other Functions

        /// <summary>
        /// If there is a start point available, returns that start point in the provided out Vector2 parameter and consumes it.
        /// 
        /// The provided Vector2 is the tile, or (row, column) position of the start point, NOT the pixel position.
        /// </summary>
        /// <param name="nextStartPoint">The Vector2 variable to have the next available start point assigned to.</param>
        /// <returns>True if there is a start point available.</returns>
        public bool ConsumeNextPlayerStartPoint(out Vector2 nextStartPoint)
        {
            foreach (Vector2 startPoint in m_playerStartPoints)
            {
                if (startPoint != null)
                {
                    nextStartPoint = startPoint;
                    m_playerStartPoints.Remove(startPoint);
                    return true;
                }
            }

            nextStartPoint = new Vector2(-1, -1);

            return false;
        }

        /// <summary>
        /// Determines if the provided position exists in the world.
        /// </summary>
        /// <param name="row">The row to check.</param>
        /// <param name="column">The column to check.</param>
        /// <returns>True if the position exists in the pathfinder.</returns>
        private bool PointExists(int row, int column)
        {
            if (row < m_rows && row >= 0 && column < m_columns && column >= 0)
                return true;

            return false;
        }

        /// <summary>
        /// Permanently adds a wall to the (row, column) position of the path-finding grid.
        /// 
        /// Use AddToWallMap(int row, int column, int lifeTime) to temporarily add a wall.
        /// </summary>
        /// <param name="row">The row of the wall to be added.</param>
        /// <param name="column">The column of the wall to be added.</param>
        public void AddToWallMap(int row, int column)
        {
            m_pathFinder.AddToWallMap(row, column);
        }

        /// <summary>
        /// Permanently removes a wall from the (row, column) position of the path-finding grid.
        /// 
        /// Use RemoveFromWallMap(int row, int column, int lifeTime) to temporarily remove a wall.
        /// </summary>
        /// <param name="row">The row of the wall to be added.</param>
        /// <param name="column">The column of the wall to be added.</param>
        public void RemoveFromWallMap(int row, int column)
        {
            m_pathFinder.RemoveFromWallMap(row, column);
        }

        /// <summary>
        /// Determines if the provided position is a wall and thus unwalkable.
        /// </summary>
        /// <param name="row">The row to check.</param>
        /// <param name="column">The column to check.</param>
        /// <returns>True if the position is a wall.</returns>
        public bool IsWall(int row, int column)
        {
            return m_pathFinder.IsWall(row, column);
        }

        /// <summary>
        /// Gets the list of tiles (as Points on the grid) for a path from one tile to another.
        /// </summary>
        /// <param name="startTile">The tile, in grid coordinates, where the path starts</param>
        /// <param name="endTile">The tile, in grid coordinates where the path ends (the destination)</param>
        /// <returns>Returns a list of Points that are grid positions for the tiles needed to walk to reach the
        /// destination tile.  Returns null if either the start or end tile is invalid or if the path does not exist.  The
        /// Point list will NOT include the start tile but does include the end tile.</returns>
        public List<Point> GetPath(Point startTile, Point endTile)
        {
            return m_pathFinder.GetPath(startTile, endTile);
        }

        #endregion
    }
}